Imports System
Imports System.Text
Imports System.Collections
Imports System.Collections.Generic
Imports System.Drawing
Imports System.Drawing.Printing
Imports System.Data
Imports System.Globalization
Imports System.Windows.Forms
Imports PdfSharp.Drawing

''' <summary> 
''' Data Grid View Printer. Print functions for a datagridview, since MS didn't see fit to do it. 
''' Source: www.CodeProject.com: "Another DataGridView Printer" by Steven Griffith
''' Note: almost completely rewritten by LCW to allow multiple objects in report
''' Report header and footer sections are printed in the top/bottom margins; there is no automatic
''' vertical distance between report sections; add empty rows (or extra returns) to achieve this
''' </summary> 
Public Class clsReport

    Friend printRange As PrintRange

    Public Enum enumRepeatAllPages
        None
        Header
        Footer
    End Enum

    Private MustInherit Class clsSection
        Protected rpt As clsReport
        Friend SectionHeight As Single
        Friend SectionWidth As Single
        Friend LastRowPrinted As Integer
        Friend RowCount As Integer
        Friend RepeatAllPages As enumRepeatAllPages
        Friend rowHeights As New List(Of Single)
        Friend MustOverride Sub SetupPrint()
        Friend MustOverride Sub MeasurePrintArea(ByVal g As Object)
        Friend MustOverride Sub PrintRow(ByVal g As Object, ByRef pos As Single)
        Sub New(ByRef _rpt As clsReport)
            rpt = _rpt
        End Sub
    End Class

    Private Class clsSectionDataGridView
        Inherits clsSection

        ' the data grid view we're printing 
        Private dgv As DataGridView = Nothing
        ' print status items 
        Private RowsToPrint As IList
        Private ColsToPrint As IList
        Private RowHeaderWidth As Single = 0
        ' calculated values 
        Private colheaderheight As Single = 0
        Private coltotalwidth As Single = 0
        Private colWidths As List(Of Single)
        Friend colWidthOverrides As New List(Of Single) 'measured in 100th of an inch

        Sub New(ByRef _rpt As clsReport, ByRef _dgv As DataGridView)
            MyBase.New(_rpt)
            dgv = _dgv
        End Sub

        Private m_ProportionalColumns As Boolean = False

        ''' <summary> 
        ''' Spread the columns proportionally across the page so it fits the width. Default is false. 
        ''' </summary> 
        Public Property ProportionalColumns() As Boolean
            Get
                Return m_ProportionalColumns
            End Get
            Set(ByVal value As Boolean)
                m_ProportionalColumns = value
            End Set
        End Property

        Private m_AllowWordWrap As Boolean = False

        ''' <summary> 
        ''' If ProportionalColumns is true, will recompute row heights and wrap text. Default is false. 
        ''' </summary> 
        Public Property AllowWordWrap() As Boolean
            Get
                Return m_AllowWordWrap
            End Get
            Set(ByVal value As Boolean)
                m_AllowWordWrap = value
            End Set
        End Property

        Private m_FontSizeMult As Single = 1.0
        Private m_AutoReduceFont As Boolean = False

        ''' <summary> 
        ''' If ProportionalColumns is true, will auto-reduce font sizes to 80% or original to help improve fit
        ''' </summary> 
        Public Property AutoReduceFont() As Boolean
            Get
                Return m_AutoReduceFont
            End Get
            Set(ByVal value As Boolean)
                m_AutoReduceFont = value
            End Set
        End Property

        ''' <summary>
        ''' Allow caller to specify special overflow flag that allows overflow of text into adjoining cells. 
        ''' </summary>
        Public Property IsOverflowCell(ByVal columnIndex As Integer, ByVal rowIndex As Integer) As Boolean
            Get
                Return dgv.Item(columnIndex, rowIndex).Tag = "Overflow"
            End Get
            Set(ByVal value As Boolean)
                dgv.Item(columnIndex, rowIndex).Tag = IIf(value, "Overflow", Nothing)
            End Set
        End Property

        Friend Overrides Sub SetupPrint()
            '----------------------------------------------------------------- 
            ' set up the rows and columns to print 
            '----------------------------------------------------------------- 

            ' rows to print (handles "selection" and "current page" options 
            If rpt.printRange = printRange.Selection Then
                ' if DGV has rows selected, it's easy, selected rows and all visible columns 
                If 0 <> dgv.SelectedRows.Count Then
                    RowsToPrint = dgv.SelectedRows
                    ColsToPrint = New List(Of Object)(dgv.Columns.Count)
                    For Each col As DataGridViewColumn In dgv.Columns
                        If col.Visible Then
                            ColsToPrint.Add(col)
                        End If
                    Next
                ElseIf 0 <> dgv.SelectedColumns.Count Then
                    ' if selected columns, then all rows, and selected columns 
                    RowsToPrint = dgv.Rows
                    ColsToPrint = dgv.SelectedColumns
                Else
                    ' we just have a bunch of selected cells so we have to do some work 
                    ' set up sorted lists. the selectedcells method does not guarantee 
                    ' that the cells will always be in left-right top-bottom order. 
                    Dim temprowstoprint As New SortedList(dgv.SelectedCells.Count)
                    Dim tempcolstoprint As New SortedList(dgv.SelectedCells.Count)

                    ' for each selected cell, add unique rows and columns 
                    Dim colindex As Integer, rowindex As Integer
                    For Each cell As DataGridViewCell In dgv.SelectedCells
                        colindex = cell.ColumnIndex
                        rowindex = cell.RowIndex

                        If Not temprowstoprint.Contains(rowindex) Then
                            temprowstoprint.Add(rowindex, dgv.Rows(rowindex))
                        End If

                        If Not tempcolstoprint.Contains(colindex) Then
                            tempcolstoprint.Add(colindex, dgv.Columns(colindex))
                        End If
                    Next

                    ' Move the now-sorted columns and rows to our list of what to print 
                    RowsToPrint = New List(Of Object)(temprowstoprint.Count)
                    For Each item As Object In temprowstoprint.Values
                        RowsToPrint.Add(item)
                    Next
                    ColsToPrint = New List(Of Object)(tempcolstoprint.Count)
                    For Each item As Object In tempcolstoprint.Values
                        ColsToPrint.Add(item)
                    Next
                End If
            ElseIf rpt.printRange = printRange.CurrentPage Then
                ' if current page was selected, print visible columns for the 
                ' displayed rows 
                ' create lists 
                RowsToPrint = New List(Of Object)(dgv.DisplayedRowCount(True))
                ColsToPrint = New List(Of Object)(dgv.Columns.Count)
                For i As Integer = dgv.FirstDisplayedScrollingRowIndex To dgv.FirstDisplayedScrollingRowIndex + dgv.DisplayedRowCount(True) - 1

                    ' select all visible rows on displayed page 
                    Dim row As DataGridViewRow = dgv.Rows(i)
                    If row.Visible Then
                        RowsToPrint.Add(row)
                    End If
                Next

                ' select all visible columns 
                ColsToPrint = New List(Of Object)(dgv.Columns.Count)
                For Each col As DataGridViewColumn In dgv.Columns
                    If col.Visible Then
                        ColsToPrint.Add(col)
                    End If
                Next
            Else
                ' this is the default for print all - everything marked visible will be printed 
                ' select all visible rows and all visible columns 
                RowsToPrint = New List(Of Object)(dgv.Rows.Count)
                For Each row As DataGridViewRow In dgv.Rows
                    If row.Visible Then
                        RowsToPrint.Add(row)
                    End If
                Next

                ColsToPrint = New List(Of Object)(dgv.Columns.Count)
                For Each col As DataGridViewColumn In dgv.Columns
                    If col.Visible Then
                        ColsToPrint.Add(col)
                    End If
                Next
            End If
            LastRowPrinted = -1
        End Sub

        ''' <summary> 
        ''' Scan all the rows and columns to be printed and calculate the 
        ''' overall individual column width (based on largest column value), 
        ''' the header sizes, and determine all the row heights. 
        ''' </summary> 
        ''' <param name="g">The graphics context for all measurements</param> 
        Friend Overrides Sub MeasurePrintArea(ByVal g As Object)
            Dim i As Integer, j As Integer

            'added by LCW--columns MUST have names
            For ii As Integer = 0 To dgv.ColumnCount - 1
                With dgv.Columns(ii)
                    If .Name = "" Then .Name = "Col" & ii
                End With
            Next
            m_FontSizeMult = 1.0

looptop:

            rowHeights = New List(Of Single)(RowsToPrint.Count)
            colWidths = New List(Of Single)(ColsToPrint.Count)

            ' temp variables 
            Dim col As DataGridViewColumn
            Dim row As DataGridViewRow

            '----------------------------------------------------------------- 
            ' measure the page headers and footers, including the grid column header cells 
            '----------------------------------------------------------------- 

            ' measure the column headers 
            Dim headerfont As Font = dgv.ColumnHeadersDefaultCellStyle.Font
            If headerfont Is Nothing Then
                headerfont = dgv.DefaultCellStyle.Font
            End If
            headerfont = ScaledFont(headerfont)

            For i = 0 To ColsToPrint.Count - 1

                ' set initial column sizes based on column titles 
                col = DirectCast(ColsToPrint(i), DataGridViewColumn)

                ' measure the title for each column, keep widths and biggest height 

                'lcw: allow for embedded crlf 
                'Dim size As SizeF = MeasureString(g, col.HeaderText, headerfont)
                'colWidths.Add(Size.Width)
                'colheaderheight = (IIf(colheaderheight < Size.Height, Size.Height, colheaderheight))

                Dim maxwidth As Integer = 0
                Dim hdr As String = col.HeaderText.Replace(vbCrLf, vbCr)
                For Each s As String In hdr.Split(vbCr)
                    maxwidth = Max(maxwidth, rpt.MeasureString(g, s, headerfont).Width)
                Next
                colWidths.Add(maxwidth * 1.05) 'add 5% buffer
                colheaderheight = Max(colheaderheight, rpt.MeasureString(g, hdr.Split(vbCr)(0), headerfont).Height * hdr.Split(vbCr).Length)
            Next

            Dim headerHeight As Single = colheaderheight

            '----------------------------------------------------------------- 
            ' measure the grid to be printed ... this gets us all the row heights 
            ' and an accurate measure of column widths for the printed area 
            '----------------------------------------------------------------- 

            Dim defaultfont As Font = dgv.DefaultCellStyle.Font
            Dim cellfont As Font
            For i = 0 To RowsToPrint.Count - 1

                row = DirectCast(RowsToPrint(i), DataGridViewRow)
                rowHeights.Add(0)

                ' add row headers if they're visible 
                If dgv.RowHeadersVisible Then
                    Dim rhsize As SizeF = rpt.MeasureString(g, row.HeaderCell.EditedFormattedValue.ToString(), headerfont)
                    RowHeaderWidth = IIf((RowHeaderWidth < rhsize.Width), rhsize.Width, RowHeaderWidth)
                End If
                For j = 0 To ColsToPrint.Count - 1

                    ' calculate widths for each column. We're looking for the largest width needed for 
                    ' all the rows of data. 
                    col = DirectCast(ColsToPrint(j), DataGridViewColumn)

                    cellfont = row.Cells(col.Name).InheritedStyle.Font
                    cellfont = ScaledFont(cellfont)

                    Dim size As SizeF = rpt.MeasureString(g, row.Cells(col.Name).EditedFormattedValue.ToString() & "XX", cellfont) 'ass 'XX' so don't get zero height

                    If Not IsOverflowCell(col.Index, row.Index) Then
                        colWidths(j) = (IIf(colWidths(j) < size.Width, size.Width, colWidths(j)))
                    End If

                    'lcw--if embedded crlf, multiply row height
                    Dim numlines As Integer = row.Cells(col.Name).EditedFormattedValue.ToString.Split(vbCrLf).Length

                    'rowHeights(i) = (IIf(rowHeights(i) < size.Height, size.Height, rowHeights(i)))
                    rowHeights(i) = (IIf(rowHeights(i) < size.Height * numlines, size.Height * numlines, rowHeights(i)))
                Next
            Next

            For c As Integer = 0 To colWidthOverrides.Count - 1
                If colWidthOverrides(c) <> 0 Then colWidths(c) = colWidthOverrides(c)
            Next

            ' total the column widths, including row headers. 
            coltotalwidth = RowHeaderWidth
            For Each w As Single In colWidths
                coltotalwidth += w
            Next

            ' if we're stretching the columns across the page, recalculate each width and 
            ' set the total width to the printable width of the page. 
            If m_ProportionalColumns Then
                Dim ratio As Single = CSng(rpt.printWidth) / CSng(coltotalwidth)

                If m_AutoReduceFont And ratio < 1 Then 'try to reduce font size by 10%
                    m_FontSizeMult *= 0.9
                    GoTo looptop
                End If

                ' adjust row header width 
                RowHeaderWidth = RowHeaderWidth * ratio
                For i = 0 To colWidths.Count - 1
                    colWidths(i) = colWidths(i) * ratio
                Next

                ' adjust data cells widths 

                ' and, by definition the total width is the print width 
                coltotalwidth = rpt.printWidth

                'If m_AllowWordWrap Then
                '    'adjust row heights to allow for word wrapping
                '    For i = 0 To RowsToPrint.Count - 1
                '        For j = 0 To ColsToPrint.Count - 1
                '            row = DirectCast(RowsToPrint(i), DataGridViewRow)
                '            cellfont = row.Cells(j).InheritedStyle.Font
                '            rowHeights(i) = Max(rowHeights(i), rpt.MeasureStringHeight(g, row.Cells(j).EditedFormattedValue.ToString(), cellfont, colWidths(j)))
                '        Next
                '    Next
                'End If
            End If

            If m_AllowWordWrap Then
                'adjust row heights to allow for word wrapping
                For i = 0 To RowsToPrint.Count - 1
                    For j = 0 To ColsToPrint.Count - 1
                        row = DirectCast(RowsToPrint(i), DataGridViewRow)
                        cellfont = row.Cells(j).InheritedStyle.Font
                        rowHeights(i) = Max(rowHeights(i), rpt.MeasureStringHeight(g, row.Cells(j).EditedFormattedValue.ToString(), cellfont, colWidths(j)))
                    Next
                Next
            End If

            RowCount = rowHeights.Count
        End Sub

        ''' <summary> 
        ''' Print the column headers. Most printing format info is retrieved from the 
        ''' source DataGridView. 
        ''' </summary> 
        ''' <param name="g">Graphics Context to print within</param> 
        ''' <param name="pos">Track vertical space used; 'y' location</param> 
        Private Sub PrintColumnHeaders(ByVal g As Object, ByRef pos As Single)
            '----------------------------------------------------------------- 
            ' setup printing 
            '----------------------------------------------------------------- 

            ' track printing location across the page. start position is hard left, 
            ' adjusted for the row headers. Note rowheaderwidth is 0 if row headers are not printed 
            Dim xcoord As Single = rpt.m_PrintMargins.Left + RowHeaderWidth

            ' set up colors, fonts for header printing 
            Dim foreground As Color = IIf(Not (dgv.ColumnHeadersDefaultCellStyle.ForeColor.IsEmpty), dgv.ColumnHeadersDefaultCellStyle.ForeColor, dgv.DefaultCellStyle.ForeColor)

            Dim background As Color = IIf(Not (dgv.ColumnHeadersDefaultCellStyle.BackColor.IsEmpty), dgv.ColumnHeadersDefaultCellStyle.BackColor, dgv.DefaultCellStyle.BackColor)

            Dim font As Font = IIf((dgv.ColumnHeadersDefaultCellStyle.Font IsNot Nothing), dgv.ColumnHeadersDefaultCellStyle.Font, dgv.DefaultCellStyle.Font)
            font = ScaledFont(font)

            ' define print rectangle 
            Dim printarea As New RectangleF(xcoord, pos, coltotalwidth - RowHeaderWidth, colheaderheight)

            ' set brushes for printing 
            Dim forebrush As New SolidBrush(foreground)
            Dim backbrush As New SolidBrush(background)

            ' set the pen for drawing the grid lines 
            Dim lines As New Pen(dgv.GridColor, 1)

            '----------------------------------------------------------------- 
            ' Now that we're all setup, start printing the column headers 
            '----------------------------------------------------------------- 

            ' fill in the background 
            If rpt.sendToPDF Then
                With CType(g, XGraphics)
                    .DrawRectangle(backbrush, printarea)
                    .DrawRectangle(lines, printarea)
                End With
            Else
                With CType(g, Graphics)
                    .FillRectangle(backbrush, printarea)
                End With
            End If

            ' write and draw each cell 
            Dim col As DataGridViewColumn
            For i As Integer = 0 To ColsToPrint.Count - 1
                col = DirectCast(ColsToPrint(i), DataGridViewColumn)

                ' set print area for this individual cell 
                Dim cellprintarea As New RectangleF(xcoord, pos, colWidths(i), colheaderheight)

                ' set the printing format for individual header cells 
                Dim format As Object = rpt.ManageStringFormat(col.HeaderCell.InheritedStyle.Alignment, False)

                ' draw the text 
                'g.DrawString(col.HeaderText, GetFont(font), forebrush, cellprintarea, format)

                'lcw--do following to allow multi-row cells if have embedded vbcr or vbcrlf
                Dim text As String = col.HeaderText
                For Each s As String In text.Replace(vbCrLf, vbCr).Split(vbCr)

                    g.DrawString(s, rpt.GetFont(font), forebrush, cellprintarea, format)
                    cellprintarea.Offset(0, rpt.MeasureString(g, s, font, format).Height)
                Next

                ' draw the borders - default to the dgv's border setting 
                If dgv.ColumnHeadersBorderStyle <> DataGridViewHeaderBorderStyle.None Then
                    If rpt.sendToPDF Then
                        CType(g, XGraphics).DrawRectangle(lines, xcoord, pos, colWidths(i), colheaderheight)
                    Else
                        CType(g, Graphics).DrawRectangle(lines, xcoord, pos, colWidths(i), colheaderheight)
                    End If
                End If

                xcoord += colWidths(i)
            Next

            ' all done, consume "used" vertical space 
            pos += colheaderheight
        End Sub

        ''' <summary> 
        ''' Print one row of the DataGridView. Most printing format info is retrieved 
        ''' from the DataGridView. 
        ''' </summary> 
        ''' <param name="g">Graphics Context to print within</param> 
        ''' <param name="pos">Track vertical space used; 'y' location</param> 
        Friend Overrides Sub PrintRow(ByVal g As Object, ByRef pos As Single)

            '----------------------------------------------------------------- 
            ' setup printing 
            '----------------------------------------------------------------- 

            ' track printing location across the page 
            Dim xcoord As Single = rpt.m_PrintMargins.Left
            LastRowPrinted += 1

            Dim row As DataGridViewRow = dgv.Rows(LastRowPrinted)

            ' set up colors, fonts for header printing 
            Dim foreground As Color = IIf(Not (row.DefaultCellStyle.ForeColor.IsEmpty), row.DefaultCellStyle.ForeColor, dgv.DefaultCellStyle.ForeColor)

            Dim background As Color = IIf(Not (row.DefaultCellStyle.BackColor.IsEmpty), row.DefaultCellStyle.BackColor, dgv.DefaultCellStyle.BackColor)

            ' define print rectangle 
            Dim printarea As New RectangleF(xcoord, pos, coltotalwidth, rowHeights(LastRowPrinted))

            ' set brushes for printing 
            Dim forebrush, backbrush As Object
            If rpt.sendToPDF Then
                forebrush = New XSolidBrush(foreground)
                backbrush = New XSolidBrush(background)
            Else
                forebrush = New SolidBrush(foreground)
                backbrush = New SolidBrush(background)
            End If

            ' set the pen for drawing the grid lines 
            Dim lines As New Pen(dgv.GridColor, 1)

            '----------------------------------------------------------------- 
            ' Now that we're all setup, start printing the row as individual cells 
            '----------------------------------------------------------------- 

            ' fill in the row background 
            If rpt.sendToPDF Then
                With CType(g, XGraphics)
                    .DrawRectangle(backbrush, printarea)
                    .DrawRectangle(lines, printarea)
                End With
            Else
                With CType(g, Graphics)
                    .FillRectangle(backbrush, printarea)
                End With
            End If

            ' print row headers 
            If dgv.RowHeadersVisible Then
                ' set printing format for the row headers 
                Dim RowHeaderFormat As Object = rpt.ManageStringFormat(dgv.RowHeadersDefaultCellStyle.Alignment, False)

                ' set print area for this individual cell 
                Dim HeaderCellPrintArea As New RectangleF(xcoord, pos, RowHeaderWidth, rowHeights(LastRowPrinted))

                ' draw the text for the row header cell 
                Dim font As Object = rpt.GetFont(row.HeaderCell.InheritedStyle.Font)
                g.DrawString(row.HeaderCell.EditedFormattedValue.ToString(), font, forebrush, HeaderCellPrintArea, RowHeaderFormat)

                ' draw the borders - default to the dgv's border setting 
                If dgv.RowHeadersBorderStyle <> DataGridViewHeaderBorderStyle.None Then
                    If rpt.sendToPDF Then
                        CType(g, XGraphics).DrawRectangle(lines, xcoord, pos, RowHeaderWidth, rowHeights(LastRowPrinted))
                    Else
                        CType(g, Graphics).DrawRectangle(lines, xcoord, pos, RowHeaderWidth, rowHeights(LastRowPrinted))
                    End If
                End If

                ' track horizontal space used 
                xcoord += RowHeaderWidth
            End If

            Dim xxcoord As Single = xcoord
            For i As Integer = 0 To ColsToPrint.Count - 1
                ' draw the borders - default to the dgv's cell border setting (unless first column allows overflow
                If dgv.CellBorderStyle <> DataGridViewCellBorderStyle.None And Not IsOverflowCell(0, row.Index) Then
                    If rpt.sendToPDF Then
                        CType(g, XGraphics).DrawRectangle(lines, xxcoord, pos, colWidths(i), rowHeights(LastRowPrinted))
                    Else
                        CType(g, Graphics).DrawRectangle(lines, xxcoord, pos, colWidths(i), rowHeights(LastRowPrinted))
                    End If
                End If
                xxcoord += colWidths(i)
            Next

            ' write and draw each cell 
            Dim col As DataGridViewColumn
            For i As Integer = 0 To ColsToPrint.Count - 1

                ' access the column being printed 
                col = DirectCast(ColsToPrint(i), DataGridViewColumn)

                ' set print area for this individual cell 
                Dim cellprintarea As New RectangleF(xcoord, pos, colWidths(i), rowHeights(LastRowPrinted))

                Dim cell As DataGridViewCell = row.Cells(col.Index)
                Dim Format As Object = rpt.ManageStringFormat(cell.InheritedStyle.Alignment, False)


                ' draw the borders - default to the dgv's border setting 
                Dim cellrect As New RectangleF(xcoord, pos, colWidths(i), rowHeights(LastRowPrinted))

                Dim font As Font = cell.InheritedStyle.Font
                font = ScaledFont(font)

                Dim text As String = cell.EditedFormattedValue.ToString()

                ' if allow cell overlap.....
                Dim IsOverflow As Boolean = IsOverflowCell(col.Index, row.Index)
                If IsOverflow Then
                    cellprintarea.Width = 0 'so doesn't clip

                    'must fill background of text so can't see vertical cell lines
                    cellrect.Width = rpt.MeasureString(g, text, font, Format).Width
                    cellrect.Offset(lines.Width, lines.Width)
                    If rpt.sendToPDF Then
                        CType(g, XGraphics).DrawRectangle(backbrush, cellrect)
                    Else
                        CType(g, Graphics).FillRectangle(backbrush, cellrect)
                    End If
                End If

                ' draw the text for the cell at the row / col intersection 
                If row.Cells(col.Index).EditedFormattedValue IsNot Nothing Then
                    For Each s As String In text.Replace(vbCrLf, vbCr).Split(vbCr)
                        g.DrawString(s, rpt.GetFont(font), forebrush, cellprintarea, Format)
                        cellprintarea.Offset(0, rpt.MeasureString(g, s, font, Format).Height)
                    Next
                End If

                ' track horizontal space used 
                xcoord += colWidths(i)
            Next

            ' all done, consume "used" vertical space 
            pos += rowHeights(LastRowPrinted)
        End Sub

        Private Function ScaledFont(ByVal OrigFont As Font) As Font
            If m_FontSizeMult = 1 Or OrigFont Is Nothing Then Return OrigFont
            With OrigFont
                Return New Font(.FontFamily, CSng(.Size * m_FontSizeMult), .Style)
            End With
        End Function

    End Class

    ''' <summary>
    ''' If used for page header or footer, tab characters treated as separator for left, center, and right-aligned portions of string
    ''' and textbox alignment is ignored. Also, %p is replaced with page number and %d is replaced with date and time.
    ''' </summary>
    ''' <remarks></remarks>
    Private Class clsSectionTextBox
        Inherits clsSection

        Private txt As TextBox
        Private rows() As String

        Sub New(ByRef _rpt As clsReport, ByRef _txt As TextBox, ByVal _repeat As enumRepeatAllPages)
            MyBase.New(_rpt)
            txt = _txt
            RepeatAllPages = _repeat
        End Sub

        Friend Overrides Sub SetupPrint()
            rows = txt.Text.Replace(vbCrLf, vbCr).Split(vbCr)
            RowCount = rows.Length
            LastRowPrinted = -1
        End Sub

        ''' <summary> 
        ''' Scan all the rows and determine all the row heights. 
        ''' </summary> 
        ''' <param name="g">The graphics context for all measurements</param> 
        Friend Overrides Sub MeasurePrintArea(ByVal g As Object)
            SectionHeight = 0
            For Each row As String In rows
                Dim ht As Single = rpt.MeasureString(g, row, txt.Font).Height
                rowHeights.Add(ht)
                SectionHeight += ht
            Next
            SectionWidth = rpt.printWidth
        End Sub

        Friend Overrides Sub PrintRow(ByVal g As Object, ByRef pos As Single)
            LastRowPrinted += 1

            ' string formatting setup 
            Dim printformat As Object = rpt.ManageStringFormat(txt.TextAlign, True)

            ' measure string 
            Dim printsize As SizeF = rpt.MeasureString(g, IIf(rows(LastRowPrinted) <> "", rows(LastRowPrinted), "X"), txt.Font, printformat)

            ' build area to print within
            Dim printarea As New RectangleF(CSng(rpt.m_PrintMargins.Left), pos, CSng(rpt.printWidth), printsize.Height)

            ' do the actual print (if tabs found, assumes is header or footer and want a left, center and right component)
            Dim s() As String = rows(LastRowPrinted).Split(vbTab)
            If s.Length = 1 Then
                If Not String.IsNullOrEmpty(s(0)) Then g.DrawString(s(0), rpt.GetFont(txt.Font), New SolidBrush(txt.ForeColor), printarea, printformat)
            Else
                For i As Integer = 0 To s.Length - 1
                    s(i) = s(i).Replace("%p", rpt.CurrentPage).Replace("%d", Now.ToString("MM/dd/yy HH:mm"))
                    Dim align As StringAlignment = Choose(i + 1, StringAlignment.Near, StringAlignment.Center, StringAlignment.Far)
                    printformat = rpt.ManageStringFormat(align, False)
                    If Not String.IsNullOrEmpty(s(i)) Then g.DrawString(s(i), rpt.GetFont(txt.Font), New SolidBrush(txt.ForeColor), printarea, printformat)
                Next
            End If

            ' track "used" vertical space 
            pos += printarea.Height
        End Sub
    End Class

    Private Class clsSectionBitmap
        Inherits clsSection

        Private bm As Bitmap

        Sub New(ByRef _rpt As clsReport, ByVal _bm As Bitmap)
            MyBase.New(_rpt)
            bm = _bm
        End Sub

        Friend Overrides Sub SetupPrint()

        End Sub

        Friend Overrides Sub MeasurePrintArea(ByVal g As Object)

        End Sub

        Friend Overrides Sub PrintRow(ByVal g As Object, ByRef pos As Single)

        End Sub
    End Class

    Private Class clsSectionSpace
        Inherits clsSection

        Private space As Single

        Sub New(ByRef _rpt As clsReport, ByVal _space As Single)
            MyBase.New(_rpt)
            space = _space
        End Sub

        Friend Overrides Sub MeasurePrintArea(ByVal g As Object)
            rowHeights.Add(space)
            SectionHeight = space
            SectionWidth = rpt.printWidth
        End Sub

        Friend Overrides Sub PrintRow(ByVal g As Object, ByRef pos As Single)
            LastRowPrinted += 1
            pos += space * 100
        End Sub

        Friend Overrides Sub SetupPrint()
            RowCount = 1
            LastRowPrinted = -1
        End Sub
    End Class

#Region "Global variables"

    ' print document 
    Private printDoc As PrintDocument = Nothing

    Private fromPage As Integer = 0
    Private toPage As Integer = -1

    ' page formatting options 
    Private pageHeight As Integer = 0
    Private pageWidth As Integer = 0
    Private printWidth As Integer = 0
    Private CurrentPage As Integer = 0
    Private CurrentSection As Integer = 0

    ' flag to indicate if should be sent to PDF
    Private sendToPDF As Boolean = False

    Private sectionList As New Generic.List(Of clsSection)

#End Region

    ''' <summary>
    ''' Add new textbox report section
    ''' </summary>
    ''' <param name="Textbox">Textbox control</param>
    ''' <param name="Repeat">Setting to determine whether this textbox repeats on all pages</param>
    Public Sub AddTextbox(ByVal Textbox As TextBox, ByVal Repeat As enumRepeatAllPages)
        sectionList.Add(New clsSectionTextBox(Me, Textbox, Repeat))
    End Sub

    ''' <summary>
    ''' Add a new textbox report section
    ''' </summary>
    ''' <param name="Text">Text string (converts \n to CR)</param>
    ''' <param name="Font">Font assigned to all text</param>
    ''' <param name="ForeColor">Color of all text</param>
    ''' <param name="Alignment">Text alignment</param>
    ''' <param name="Repeat">Setting to determine whether this textbox repeats on all pages</param>
    Public Sub AddTextbox(ByVal Text As String, ByVal Font As Font, ByVal ForeColor As Color, ByVal Alignment As HorizontalAlignment, ByVal Repeat As clsReport.enumRepeatAllPages)
        Dim txt As New TextBox
        With txt
            .Text = Text.Replace("\n", vbCr).Replace("\t", vbTab)
            .Font = Font
            .ForeColor = ForeColor
            .TextAlign = Alignment
            AddTextbox(txt, Repeat)
        End With
    End Sub


    ''' <summary>
    ''' Add a new textbox report section having the specified text and font size (assigns centered, bold, Tahoma font).
    ''' This section will not repeat on all pages
    ''' </summary>
    ''' <param name="Text">Text string (converts \n to CR and \t to TAB for headers/footers--%p and %d are replaced with page number and date/time)</param>
    ''' <param name="FontSize">Font size in points</param>
    Public Sub AddTextbox(ByVal Text As String, ByVal FontSize As Single)
        AddTextbox(Text, New Font("Tahoma", FontSize, FontStyle.Bold, GraphicsUnit.Point), Color.Black, HorizontalAlignment.Center, enumRepeatAllPages.None)
    End Sub

    ''' <summary>
    ''' Add a new datagridview report section
    ''' </summary>
    ''' <param name="Dgv">Datagridview</param>
    ''' <remarks></remarks>
    Public Sub AddDataGridView(ByVal Dgv As DataGridView)
        sectionList.Add(New clsSectionDataGridView(Me, Dgv))
    End Sub

    ''' <summary>
    ''' Add a new datagridview report section and specify advanced options
    ''' </summary>
    ''' <param name="Dgv">Datagridview</param>
    ''' <param name="ProportionalColumns">If true, will widen or shrink column to make them fill printable page width</param>
    ''' <param name="AutoReduceFont">If true and grid would be wider than page width, with automatically reduce font size until it fits</param>
    ''' <param name="AllowWordWrap">If true will allow cell content to autowrap in cell (proportional columns must be true)</param>
    ''' <param name="ColumnWidthOverrides">If present will force column widths to specified values (array of widths in inches); only need to supply as many as desired, starting from the left; if zero will ignore override</param>
    ''' <remarks></remarks>
    Public Sub AddDataGridView(ByRef Dgv As DataGridView, ByVal ProportionalColumns As Boolean, ByVal AutoReduceFont As Boolean, ByVal AllowWordWrap As Boolean, Optional ByVal ColumnWidthOverrides() As Single = Nothing)
        Dim dgsect As New clsSectionDataGridView(Me, Dgv)
        With dgsect
            .ProportionalColumns = ProportionalColumns
            .AutoReduceFont = AutoReduceFont
            .AllowWordWrap = AllowWordWrap
            If ColumnWidthOverrides IsNot Nothing Then
                For Each v As Single In ColumnWidthOverrides
                    .colWidthOverrides.Add(v * 100)
                Next
            End If
        End With
        sectionList.Add(dgsect)
    End Sub

    ''' <summary>
    ''' This report section consists on only vertical space
    ''' </summary>
    ''' <param name="VerticalSpace">Vertical gap to introduce in inches</param>
    Public Sub AddSpace(ByVal VerticalSpace As Single)
        sectionList.Add(New clsSectionSpace(Me, VerticalSpace))
    End Sub

#Region "Properties"

#Region "Page level properties"

    Private m_PrintMargins As Margins

    Private m_ForceLandscape As Boolean = False

    ''' <summary> 
    ''' Force the report to be printed in landscape mode
    ''' </summary> 
    Public Property ForceLandscape() As Boolean
        Get
            Return m_ForceLandscape
        End Get
        Set(ByVal value As Boolean)
            m_ForceLandscape = value
        End Set
    End Property

#End Region

#End Region

    ''' <summary> 
    ''' Constructor for clsReport
    ''' </summary> 
    Public Sub New(ByRef _PrintDoc As PrintDocument)
        Initialize(_PrintDoc)
    End Sub

    Public Sub New()

    End Sub

    Public Sub Initialize(ByRef _PrintDoc As PrintDocument)

        ' create print document 
        If _PrintDoc Is Nothing Then printDoc = New PrintDocument() Else printDoc = _PrintDoc

        'AddHandler printDoc.PrintPage, AddressOf printDoc_PrintPage
        m_PrintMargins = New Margins(60, 60, 40, 40)

    End Sub

    ''' <summary> 
    ''' Start the printing process, print to a printer. 
    ''' </summary> 
    Public Sub PrintReport(Optional ByVal Preview As Boolean = False)

        Dim res As DialogResult

        AddHandler printDoc.PrintPage, AddressOf printDoc_PrintPage

        ' show print dialog 
        If Preview Then
            Dim pp As New MyPrintPreviewDialog(printDoc)
            pp.Document = printDoc
            Do
                SetupPrint()
                res = pp.ShowDialog
            Loop Until res <> DialogResult.Retry
            pp.Dispose()
        Else
            ' create new print dialog and set options 
            Dim pd As New PrintDialog()
            pd.Document = printDoc
            printDoc.DefaultPageSettings.Margins = m_PrintMargins
            pd.AllowSelection = True
            pd.AllowSomePages = True
            pd.AllowCurrentPage = True
            pd.AllowPrintToFile = False
            res = pd.ShowDialog
        End If
        If res = DialogResult.OK Then
            SetupPrint()
            printDoc.Print()
        ElseIf res = DialogResult.Abort Then
            PrintReportToPDF(True)
        End If

        RemoveHandler printDoc.PrintPage, AddressOf printDoc_PrintPage
    End Sub

    ''' <summary> 
    ''' Start the printing process, print to a PDF document. 
    ''' </summary> 
    Public Sub PrintReportToPDF(Optional ByVal Preview As Boolean = False)

        ' create new print dialog and set options 
        Dim dlg As New SaveFileDialog
        With dlg
            .AddExtension = True
            .DefaultExt = "PDF"
            .Filter = "PDF File (*.pdf)|*.pdf"
            .OverwritePrompt = False
            .SupportMultiDottedExtensions = False
            .Title = "Save to PDF"
            If .ShowDialog = DialogResult.OK Then
                'convert margins to points
                With m_PrintMargins
                    .Left *= 0.72
                    .Right *= 0.72
                    .Top *= 0.72
                    .Bottom *= 0.72
                End With

                Dim Append As Boolean = False

                If My.Computer.FileSystem.FileExists(.FileName) Then
                    Select Case MessageBox.Show(String.Format("The file '{0}' already exists. Do you want to overwrite it?\n\nSelect 'Yes' to overwrite, 'No' to append to the existing file.".Replace("\n", vbCr), .FileName), "Warning", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question)
                        Case DialogResult.Yes : My.Computer.FileSystem.DeleteFile(.FileName) : Append = False
                        Case DialogResult.No : Append = True
                        Case DialogResult.Cancel : Exit Sub
                    End Select
                End If
                Dim pdf As PdfSharp.Pdf.PdfDocument
                Dim tempfile As String = My.Computer.FileSystem.GetTempFileName
                If Append Then
                    pdf = PdfSharp.Pdf.IO.PdfReader.Open(.FileName, PdfSharp.Pdf.IO.PdfDocumentOpenMode.Modify)
                Else
                    pdf = New PdfSharp.Pdf.PdfDocument(.FileName)
                End If
                Dim pdfpage As PdfSharp.Pdf.PdfPage
                Dim HasMorePages As Boolean = True
                sendToPDF = True
                Dim gfx As PdfSharp.Drawing.XGraphics
                For Each section As clsSection In sectionList
                    section.SetupPrint()
                Next
                Do While HasMorePages
                    pdfpage = pdf.AddPage
                    pdfpage.Orientation = IIf(printDoc.PrinterSettings.DefaultPageSettings.Landscape, PdfSharp.PageOrientation.Landscape, PdfSharp.PageOrientation.Portrait)
                    SetupPDF(pdf, pdfpage)
                    gfx = PdfSharp.Drawing.XGraphics.FromPdfPage(pdfpage)
                    PrintPage(gfx, HasMorePages)
                Loop
                Try
                    If Append Then
                        pdf.Save(.FileName.Replace(".pdf", "new.pdf"))
                        pdf.Close()
                    Else
                        pdf.Close()
                        'pdf.Save(.FileName)
                    End If
                Catch ex As Exception
                    MessageBox.Show("Unable to save PDF file; it is likely already open by another user.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Error)
                    .Dispose()
                    Exit Sub
                Finally
                    pdf.Dispose()
                    pdf = Nothing
                End Try
                If Preview Then Process.Start(.FileName)
            End If
            Process.Start(.FileName)
            .Dispose()
        End With
    End Sub

    ''' <summary> 
    ''' Set up the print job. Save information from print dialog 
    ''' and print document for easy access. Also sets up the rows 
    ''' and columns that will be printed. 
    ''' </summary> 
    Private Sub SetupPrint()
        '----------------------------------------------------------------- 
        ' save data from print dialog and document 
        '----------------------------------------------------------------- 

        If m_ForceLandscape Then printDoc.DefaultPageSettings.Landscape = True

        ' check to see if we're doing landscape printing 
        If printDoc.DefaultPageSettings.Landscape Then
            ' landscape: switch width and height 
            pageHeight = printDoc.DefaultPageSettings.PaperSize.Width
            pageWidth = printDoc.DefaultPageSettings.PaperSize.Height
        Else
            ' portrait: keep width and height as expected 
            pageHeight = printDoc.DefaultPageSettings.PaperSize.Height
            pageWidth = printDoc.DefaultPageSettings.PaperSize.Width
        End If

        ' save printer margins and calc print width 
        m_PrintMargins = printDoc.DefaultPageSettings.Margins
        printWidth = pageWidth - m_PrintMargins.Left - m_PrintMargins.Right

        ' save print range 
        printRange = printDoc.PrinterSettings.PrintRange

        ' pages to print handles "some pages" option 
        If printRange.SomePages = printRange Then
            ' set limits to only print some pages 
            fromPage = printDoc.PrinterSettings.FromPage
            toPage = printDoc.PrinterSettings.ToPage
        Else
            ' set extremes so that we'll print all pages 
            fromPage = 0
            toPage = Integer.MaxValue
        End If
        For Each section As clsSection In sectionList
            section.SetupPrint()
        Next
        CurrentSection = 0
        CurrentPage = 0
    End Sub

    ''' <summary> 
    ''' Set up the print job. Save information from print dialog 
    ''' and print document for easy access. Also sets up the rows 
    ''' and columns that will be printed. 
    ''' </summary> 
    Private Sub SetupPDF(ByVal pdf As PdfSharp.Pdf.PdfDocument, ByVal pdfPage As PdfSharp.Pdf.PdfPage)

        If m_ForceLandscape Then
            pdfPage.Orientation = PdfSharp.PageOrientation.Landscape
            'pdfPage.Rotate = 90
        End If
        pdfPage.Size = PdfSharp.PageSize.Letter

        If pdfPage.Orientation = PdfSharp.PageOrientation.Landscape Then
            pdfPage.Rotate = 90
            pageWidth = pdfPage.Height.Point
            pageHeight = pdfPage.Width.Point
            printWidth = pageWidth - m_PrintMargins.Left - m_PrintMargins.Right
        Else
            pdfPage.Rotate = 0
            pageWidth = pdfPage.Width.Point
            pageHeight = pdfPage.Height.Point
            printWidth = pageWidth - m_PrintMargins.Left - m_PrintMargins.Right
        End If

        ' save print range 
        printRange = Printing.PrintRange.AllPages

        ' set extremes so that we'll print all pages 
        fromPage = 0
        toPage = Integer.MaxValue
    End Sub

    ''' <summary> 
    ''' Centralize the string format settings. Does the work of checking for user 
    ''' overrides, and if they're not present, setting the cell alignment to match 
    ''' (somewhat) the source control's string alignment. 
    ''' </summary> 
    ''' <param name="alignment">String alignment</param> 
    ''' <returns></returns> 
    Private Function ManageStringFormat(ByVal alignment As StringAlignment, ByVal wordwrap As Boolean) As Object 'StringFormat
        ' start with the provided 
        'Dim format As New StringFormat()
        Dim format As Object
        If sendToPDF Then
            format = New XStringFormat()
        Else
            format = New StringFormat()
            format.Trimming = StringTrimming.Word
        End If
        format.Alignment = alignment
        If Not wordwrap Then format.formatflags = StringFormatFlags.NoWrap
        Return format
    End Function

    ''' <summary> 
    ''' Centralize the string format settings. Does the work of checking for user 
    ''' overrides, and if they're not present, setting the cell alignment to match 
    ''' (somewhat) the source control's string alignment. 
    ''' </summary> 
    ''' <param name="alignment">String alignment</param> 
    ''' <returns></returns> 
    Private Function ManageStringFormat(ByVal alignment As DataGridViewContentAlignment, ByVal wordwrap As Boolean) As Object 'StringFormat

        ' start with the provided 
        Dim format As Object
        If sendToPDF Then
            format = New XStringFormat()
        Else
            format = New StringFormat()
            format.Trimming = StringTrimming.Word
        End If
        If alignment.ToString().Contains("Center") Then
            format.Alignment = StringAlignment.Center
        ElseIf alignment.ToString().Contains("Right") Then
            format.Alignment = StringAlignment.Far
        ElseIf alignment.ToString().Contains("Left") Then
            format.Alignment = StringAlignment.Near
        End If

        If wordwrap Then format.FormatFlags = StringFormatFlags.NoWrap
        Return format
    End Function

    ''' <summary> 
    ''' Centralize the string format settings. Does the work of checking for user 
    ''' overrides, and if they're not present, setting the cell alignment to match 
    ''' (somewhat) the source control's string alignment. 
    ''' </summary> 
    ''' <param name="alignment">String alignment</param> 
    ''' <returns></returns> 
    Private Function ManageStringFormat(ByVal alignment As HorizontalAlignment, ByVal wordwrap As Boolean) As Object 'StringFormat

        ' start with the provided 
        Dim format As Object
        If sendToPDF Then
            format = New XStringFormat()
        Else
            format = New StringFormat()
            format.Trimming = StringTrimming.Word
        End If
        If alignment.ToString().Contains("Center") Then
            format.Alignment = StringAlignment.Center
        ElseIf alignment.ToString().Contains("Right") Then
            format.Alignment = StringAlignment.Far
        ElseIf alignment.ToString().Contains("Left") Then
            format.Alignment = StringAlignment.Near
        End If

        If Not wordwrap Then format.FormatFlags = StringFormatFlags.NoWrap
        Return format
    End Function

    ''' <summary>
    ''' Device independent method to call MeasureString
    ''' </summary>
    Private Function MeasureString(ByVal g As Object, ByVal Text As String, ByVal [Font] As Font, ByVal Format As Object) As SizeF
        If sendToPDF Then
            Return CType(g, XGraphics).MeasureString(Text, GetFont(Font), CType(Format, XStringFormat)).ToSizeF
        Else
            Return CType(g, Graphics).MeasureString(Text, Font, printWidth, CType(Format, StringFormat))
        End If
    End Function

    ''' <summary>
    ''' Device independent method to call MeasureString
    ''' </summary>
    Private Function MeasureString(ByVal g As Object, ByVal Text As String, ByVal [Font] As Font) As SizeF
        If sendToPDF Then
            Return CType(g, XGraphics).MeasureString(Text, GetFont(Font), ManageStringFormat(StringAlignment.Near, True)).ToSizeF
        Else
            Return CType(g, Graphics).MeasureString(Text, Font, printWidth, ManageStringFormat(StringAlignment.Near, True))
        End If
    End Function

    ''' <summary>
    ''' Device independent method to call MeasureString
    ''' </summary>
    Private Function MeasureStringHeight(ByVal g As Object, ByVal Text As String, ByVal [Font] As Font, ByVal PrintWidth As Integer) As Single
        If sendToPDF Then
            'todo: handle multiline for PDF
            Return CType(g, XGraphics).MeasureString(Text, GetFont(Font)).ToSizeF.Height
        Else
            Return CType(g, Graphics).MeasureString(Text, Font, PrintWidth).Height
        End If
    End Function

    ''' <summary> 
    ''' PrintPage event handler. This routine prints one page. It will 
    ''' skip non-printable pages if the user selected the "some pages" option 
    ''' on the print dialog. 
    ''' </summary> 
    ''' <param name="sender">default object from windows</param> 
    ''' <param name="e">Event info from Windows about the printing</param> 
    Private Sub printDoc_PrintPage(ByVal sender As Object, ByVal e As PrintPageEventArgs)
        PrintPage(e.Graphics, e.HasMorePages)
    End Sub

    Private Sub PrintPage(ByRef graphics As Object, ByRef HasMorePages As Boolean)
        ' flag for handling printing some pages rather than all 
        Dim printthispage As Boolean = False

        ' current printing position 
        ' increment page number & check page range 
        CurrentPage += 1
        If (CurrentPage >= fromPage) AndAlso (CurrentPage <= toPage) Then
            printthispage = True
        End If

        Static headerheight As Single
        Static footerheight As Single

        ' if this is the first page, then measure the print areas 
        If CurrentPage = 1 Then
            For Each sec As clsSection In sectionList
                sec.MeasurePrintArea(graphics)
                If sec.RepeatAllPages = enumRepeatAllPages.Header Then headerheight += sec.SectionHeight
                If sec.RepeatAllPages = enumRepeatAllPages.Footer Then footerheight += sec.SectionHeight
            Next
            'set to first section that is not repeated at top of all pages
            For i As Integer = 0 To sectionList.Count - 1
                If sectionList(i).RepeatAllPages = enumRepeatAllPages.None Then CurrentSection = i : Exit For
            Next
        End If

        '----------------------------------------------------------------- 
        ' print headers 
        '----------------------------------------------------------------- 

        Dim printpos As Single = m_PrintMargins.Top - headerheight

        For i As Integer = 0 To sectionList.Count - 1
            With sectionList(i)
                If .RepeatAllPages = enumRepeatAllPages.Header Then
                    .LastRowPrinted = -1
                    While .LastRowPrinted < .RowCount - 1
                        If printthispage Then
                            .PrintRow(graphics, printpos)
                        Else
                            .LastRowPrinted += 1
                            printpos += .rowHeights(.LastRowPrinted)
                        End If
                    End While
                End If
            End With
        Next

        'continue printing next body section until page is complete
        HasMorePages = True

        Dim thisSection As clsSection = sectionList(CurrentSection)
        While thisSection.RowCount <= 1 OrElse printpos + thisSection.rowHeights(thisSection.LastRowPrinted + 1) < (pageHeight - footerheight - m_PrintMargins.Bottom)
            If printthispage Then
                thisSection.PrintRow(graphics, printpos)
            Else
                thisSection.LastRowPrinted += 1
                printpos += thisSection.rowHeights(thisSection.LastRowPrinted)
            End If

            ' bottom check
            If thisSection.LastRowPrinted >= thisSection.RowCount - 1 Then
                Dim NumRemaining As Integer = 0
                For i As Integer = CurrentSection + 1 To sectionList.Count - 1
                    If sectionList(i).RepeatAllPages = enumRepeatAllPages.None Then NumRemaining += 1
                Next
                If NumRemaining = 0 OrElse CurrentPage > toPage Then
                    ' we're done here - nothing more to print
                    HasMorePages = False
                    Exit While
                Else
                    'reset in case will print after preview
                    thisSection.LastRowPrinted = -1
                    'advance to next section
                    CurrentSection += 1
                    thisSection = sectionList(CurrentSection)
                    thisSection.LastRowPrinted = -1
                End If
            End If
        End While

        '----------------------------------------------------------------- 
        ' print footers
        '----------------------------------------------------------------- 

        printpos = pageHeight - m_PrintMargins.Bottom
        For i As Integer = 0 To sectionList.Count - 1
            With sectionList(i)
                If .RepeatAllPages = enumRepeatAllPages.Footer Then
                    .LastRowPrinted = -1
                    While .LastRowPrinted < .RowCount - 1
                        If CurrentPage >= fromPage And CurrentPage <= toPage Then
                            .PrintRow(graphics, printpos)
                        Else
                            .LastRowPrinted += 1
                            printpos += .rowHeights(.LastRowPrinted)
                        End If
                    End While
                End If
            End With
        Next

        ' reset in case print preview directly calls printdoc
        If Not hasmorepages Then SetupPrint()

        Return
    End Sub

    ''' <summary>
    ''' Sharp PDF requires that fonts use the World units; this facilitates DrawString
    ''' </summary>
    Private Function GetFont(ByVal [Font] As Font) As Object
        If sendToPDF Then
            Return New XFont(Font.Name, Font.Size, Font.Style)
        Else
            Return Font
        End If
    End Function
End Class
